<script setup>
  import { toRefs, watchEffect } from 'vue'
  import { onMounted, onUnmounted, useTemplateRef, ref, reactive, nextTick, computed } from 'vue'

  // 定义props
  const emit = defineEmits(['click-item'])
  const props = defineProps({ options: Object, readonly: Boolean })
  const rows = ref(15)
  const cols = ref(20)
  const width = ref(50)
  const data = ref([])

  // 监听更新数据
  watchEffect(() => {
    const options = props.options
    rows.value = options.height
    cols.value = options.width
    data.value = options.data
  })

  // 常量变量
  const tip = computed(() => {
    return `按住ctrl+滚轮进行缩放，按住空格+左键进行拖动${
      props.readonly ? '，点击座位预约座位' : '，左键单击选择起始点，再次单击选择结束点，Tab快速切换当前类型'
    }`
  })
  let foucsingPoint = { x: 0, y: 0, foucsing: false }
  const colors = ['#fff', '#737373', '#3b82f6', '#f59e0b']
  const types = [
    { label: '默认', value: 0 },
    { label: '桌子', value: 1 },
    { label: '座位', value: 2 },
    { label: '书架', value: 3 },
  ]
  const sizes = [
    { label: '小(20x15)', value: 0, w: 20, h: 15 },
    { label: '中(28x21)', value: 1, w: 28, h: 21 },
    { label: '大(36x27)', value: 2, w: 36, h: 27 },
  ]

  // 获取配置
  const size = ref(0)
  const type = ref(0)

  // 缩放
  const plus = () => (width.value = Math.min(100, width.value + 5))
  const minus = () => (width.value = Math.max(10, width.value - 5))
  const refresh = () => {
    foucsingPoint = { x: 0, y: 0, foucsing: false }
    let v = size.value
    size.value = null
    nextTick(() => (size.value = v))
  }
  const getMap = () => {
    return {
      height: rows.value,
      width: cols.value,
      data: data.value.map(({ x, y, type }) => ({ x, y, type })),
    }
  }

  defineExpose({ getMap })

  // 初始化数据
  watchEffect(() => {
    if (props.readonly) return
    const { w, h } = sizes[size.value]
    cols.value = w
    rows.value = h
  })

  // 刷新网格数据
  watchEffect(() => {
    const datas = data.value ?? []
    data.value = Array.from({ length: rows.value * cols.value }, (_, i) => ({
      x: i % cols.value,
      y: Math.floor(i / cols.value),
      foucsing: false,
      type: 0,
    }))

    datas.forEach(item => {
      const idx = item.x + item.y * cols.value
      data.value[idx].type = item.type
      data.value[idx].id = item.id
      data.value[idx].number = item.number
    })
  })

  // 点击单元格事件
  const onClickGridItem = item => {
    if (props.readonly) {
      emit('click-item', item)
      return
    }

    if (drag.value) return

    const { x, y } = item
    const idx = item.x + item.y * cols.value
    const foucsingIdx = foucsingPoint.x + foucsingPoint.y * cols.value

    if (!foucsingPoint.foucsing) {
      foucsingPoint = { x: item.x, y: item.y, foucsing: true }
      data.value[idx].foucsing = true
    } else {
      const startX = Math.min(foucsingPoint.x, x)
      const startY = Math.min(foucsingPoint.y, y)
      const endX = Math.max(foucsingPoint.x, x)
      const endY = Math.max(foucsingPoint.y, y)

      const t = type.value

      for (let i = startX; i <= endX; i++) {
        for (let j = startY; j <= endY; j++) {
          const idx = i + j * cols.value
          data.value[idx].type = t
        }
      }

      foucsingPoint = { x: 0, y: 0, foucsing: false }
      data.value[foucsingIdx].foucsing = false
    }
  }

  // 拖动状态
  const wrapper = useTemplateRef('wrapper')
  const drag = ref(false) // 是否启用拖动
  const dragging = ref(false) // 是否拖动中
  let sx, sy, sl, st

  /* 空格控制 drag 开关 */
  const onKeyDown = e => {
    if (e.code === 'Tab') {
      e.preventDefault()
      type.value = (type.value + 1) % 4
    }

    if (e.code === 'Space' && wrapper.value?.contains(e.target)) {
      e.preventDefault()
      drag.value = true
    }
  }
  const onKeyUp = e => {
    if (e.code === 'Space') {
      drag.value = false
      dragging.value = false
      cleanup()
    }
  }

  /* 左键按下开始拖动，松开结束 */
  const onMouseDown = e => {
    if (!drag.value || e.button !== 0) return
    dragging.value = true
    sx = e.clientX
    sy = e.clientY
    sl = wrapper.value.scrollLeft
    st = wrapper.value.scrollTop
    window.addEventListener('mousemove', onMouseMove)
    window.addEventListener('mouseup', onMouseUp)
  }
  const onMouseMove = e => {
    if (!dragging.value) return
    wrapper.value.scrollTo(sl - (e.clientX - sx), st - (e.clientY - sy))
  }
  const onMouseUp = () => {
    dragging.value = false
    cleanup()
  }

  // 滚轮缩放
  const onWheel = e => {
    if (!e.ctrlKey || !wrapper.value?.contains(e.target)) return
    e.preventDefault()
    e.deltaY < 0 ? plus() : minus()
  }

  /* 清理鼠标监听 */
  const cleanup = () => {
    window.removeEventListener('mousemove', onMouseMove)
    window.removeEventListener('mouseup', onMouseUp)
  }

  /* 绑定 / 解绑 */
  onMounted(() => {
    window.addEventListener('wheel', onWheel, { passive: false })
    window.addEventListener('keydown', onKeyDown)
    window.addEventListener('keyup', onKeyUp)
    wrapper.value.addEventListener('mousedown', onMouseDown)
  })
  onUnmounted(() => {
    window.removeEventListener('wheel', onWheel)
    window.removeEventListener('keydown', onKeyDown)
    window.removeEventListener('keyup', onKeyUp)
    cleanup()
  })
</script>

<template>
  <div class="grid-wrapper" ref="wrapper" tabindex="0" :class="{ drag: drag, dragging: dragging }">
    <!-- 工具栏 -->
    <div class="grid-toolbar" v-if="!props.readonly">
      <!-- 填充类型 -->
      <el-select style="width: 120px; margin-right: 8px" v-model="type" placeholder="选择类型">
        <el-option v-for="item in types" :key="item.value" :label="item.label" :value="item.value"></el-option>
        <template #tag>
          <el-tag>{{ typeMap[type].value }}</el-tag>
        </template>
      </el-select>
      <!-- 画布大小 -->
      <el-select style="width: 120px; margin-right: 8px" v-model="size" placeholder="选择画布大小">
        <el-option v-for="item in sizes" :key="item.value" :label="item.label" :value="item.value"></el-option>
      </el-select>
      <!-- 缩放按钮 -->
      <el-button @click="plus" icon="Plus"></el-button>
      <el-button @click="minus" icon="Minus"></el-button>
      <el-button @click="refresh" icon="Refresh">刷新</el-button>
      <slot></slot>
    </div>
    <div
      class="grid-container"
      :style="{ '--x': cols, '--y': rows, '--w': `${width}px`, '--g': readonly ? 0 : `${width / 2}px` }"
    >
      <div
        v-for="(item, idx) in data"
        :key="idx"
        class="grid-item flex-center"
        :class="{ choose: item.foucsing }"
        :style="{ background: colors[item.type] }"
        @click="onClickGridItem(item)"
      >
        <span v-show="props.readonly">
          {{ item.number }}
        </span>
      </div>
    </div>
    <div class="tip">{{ tip }}</div>
  </div>
</template>

<style scoped>
  .grid-wrapper {
    position: relative;
    overflow: auto;
    margin-top: var(--padding-2);
    border-radius: var(--round-2);
    padding: var(--padding-1);
    background-color: rgba(255, 255, 255, 0.5);
  }

  .grid-wrapper:focus {
    outline: none;
  }

  .grid-toolbar {
    position: sticky;
    top: 0;
    left: 0;
    z-index: 1;
    padding: 3px 6px;
    border-radius: var(--round-1);
    background-color: var(--primary-a50);
  }

  .grid-container {
    padding: 300px;
    padding-right: 600px;
    padding-bottom: 600px;
    width: fit-content;
    display: grid;
    gap: var(--g);
    grid-template-columns: repeat(var(--x), var(--w));
  }

  .grid-item {
    width: var(--w);
    height: var(--w);
    background-color: #fff;
    cursor: pointer;
    color: #fff;
  }

  .tip {
    font-weight: 600;
    position: fixed;
    right: 40px;
    bottom: 40px;
  }

  .choose {
    border: 4px dotted var(--primary-800);
  }
</style>
